#include "U2GLESTexture.h"

#include "U2GLESSupport.h"
#include "U2GLESPixelUtil.h"
#include "U2GLESHardwarePixelBuffer.h"
#include "U2Root.h"
#include "U2GLESRenderSystem.h"


U2EG_NAMESPACE_USING


static inline void doImageIO(const U2String &name, const U2String &group,
                             const U2String &ext,
                             std::vector<U2Image> &images,
                             U2Resource *r)
{
    size_t imgIdx = images.size();
    images.push_back(U2Image());

    U2DataStreamPtr dstream =
        U2ResourceGroupManager::getSingleton().openResource(
            name, group, true, r);

    images[imgIdx].load(dstream, ext);
}

U2GLESTexture::U2GLESTexture(U2ResourceManager* creator, const U2String& name,
                         U2ResourceHandle handle, const U2String& group, bool isManual,
                         U2ManualResourceLoader* loader, U2GLESSupport& support)
: U2Texture(creator, name, handle, group, isManual, loader)
, mTextureID(0)
, mGLSupport(support)
{
}

U2GLESTexture::~U2GLESTexture()
{
    // have to call this here rather than in U2Resource destructor
    // since calling virtual methods in base destructors causes crash
    if (isLoaded())
    {
        unload();
    }
    else
    {
        freeInternalResources();
    }
}

GLenum U2GLESTexture::getGLESTextureTarget(void) const
{
    return GL_TEXTURE_2D;
}

// Creation / loading methods
void U2GLESTexture::createInternalResourcesImpl(void)
{
	// Convert to nearest power-of-two size if required
    mWidth = U2GLESPixelUtil::optionalPO2(mWidth);
    mHeight = U2GLESPixelUtil::optionalPO2(mHeight);
    mDepth = U2GLESPixelUtil::optionalPO2(mDepth);

	// Adjust format if required
    mFormat = U2TextureManager::getSingleton().getNativeFormat(TEX_TYPE_2D, mFormat, mUsage);

	// Check requested number of mipmaps
    size_t maxMips = U2GLESPixelUtil::getMaxMipmaps(mWidth, mHeight, mDepth, mFormat);
    
    if(U2PixelUtil::isCompressed(mFormat) && (mNumMipmaps == 0))
        mNumRequestedMipmaps = 0;
    
    mNumMipmaps = mNumRequestedMipmaps;
    if (mNumMipmaps > maxMips)
        mNumMipmaps = maxMips;

	// Generate texture name
    glGenTextures(1, &mTextureID);
    GL_CHECK_ERROR;

	// Set texture type
    glBindTexture(GL_TEXTURE_2D, mTextureID);
    GL_CHECK_ERROR;

    // Set some misc default parameters, these can of course be changed later
    glTexParameteri(GL_TEXTURE_2D,
                    GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    GL_CHECK_ERROR;
    glTexParameteri(GL_TEXTURE_2D,
                    GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    GL_CHECK_ERROR;
    glTexParameteri(GL_TEXTURE_2D,
                    GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    GL_CHECK_ERROR;
    glTexParameteri(GL_TEXTURE_2D,
                    GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    GL_CHECK_ERROR;

	// If we can do automip generation and the user desires this, do so
    mMipmapsHardwareGenerated =
        U2Root::getSingleton().getRenderSystem()->getCapabilities()->hasCapability(RSC_AUTOMIPMAP) && !U2PixelUtil::isCompressed(mFormat);

    if ((mUsage & TU_AUTOMIPMAP) &&
        mNumRequestedMipmaps && mMipmapsHardwareGenerated)
    {
        glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
        GL_CHECK_ERROR;
    }

    // Allocate internal buffer so that glTexSubImageXD can be used
    // Internal format
    GLenum format = U2GLESPixelUtil::getClosestGLInternalFormat(mFormat, mHwGamma);
    GLenum datatype = U2GLESPixelUtil::getGLOriginDataType(mFormat);
    size_t width = mWidth;
    size_t height = mHeight;
    size_t depth = mDepth;

    if (U2PixelUtil::isCompressed(mFormat))
    {
        // Compressed formats
        size_t size = U2PixelUtil::getMemorySize(mWidth, mHeight, mDepth, mFormat);

        // Provide temporary buffer filled with zeroes as glCompressedTexImageXD does not
        // accept a 0 pointer like normal glTexImageXD
        // Run through this process for every mipmap to pregenerate mipmap pyramid

        u2uint8* tmpdata = new u2uint8[size];
        memset(tmpdata, 0, size);
        for (size_t mip = 0; mip <= mNumMipmaps; mip++)
        {
            size = U2PixelUtil::getMemorySize(width, height, depth, mFormat);

            glCompressedTexImage2D(GL_TEXTURE_2D,
                                   mip,
                                   format,
                                   width, height,
                                   0,
                                   size,
                                   tmpdata);
            GL_CHECK_ERROR;

//             U2LogManager::getSingleton().logMessage("GLESTexture::create - Mip: " + U2StringConverter::toString(mip) +
//                                                  " Width: " + U2StringConverter::toString(width) +
//                                                  " Height: " + U2StringConverter::toString(height) +
//                                                  " Internal Format: " + U2StringConverter::toString(format)
//                                                  );

            if(width > 1)
            {
                width = width / 2;
            }
            if(height > 1)
            {
                height = height / 2;
            }
            if(depth > 1)
            {
                depth = depth / 2;
            }
        }
        delete [] tmpdata;
    }
    else
    {
        // Run through this process to pregenerate mipmap pyramid
        for(size_t mip = 0; mip <= mNumMipmaps; mip++)
        {
            glTexImage2D(GL_TEXTURE_2D,
                         mip,
                         format,
                         width, height,
                         0,
                         format,
                         datatype, 0);
            GL_CHECK_ERROR;

            if (width > 1)
            {
                width = width / 2;
            }
            if (height > 1)
            {
                height = height / 2;
            }
        }
    }

    _createSurfaceList();

    // Get final internal format
    mFormat = getBuffer(0,0)->getFormat();
}

void U2GLESTexture::createRenderTexture(void)
{
    // Create the GL texture
    // This already does everything necessary
    createInternalResources();
}

void U2GLESTexture::prepareImpl()
{
    if (mUsage & TU_RENDERTARGET) return;

    U2String baseName, ext;
    size_t pos = mName.find_last_of(".");
    baseName = mName.substr(0, pos);

    if (pos != U2String::npos)
    {
        ext = mName.substr(pos+1);
    }

    LoadedImages loadedImages = LoadedImages(new std::vector<U2Image>());

    if (mTextureType == TEX_TYPE_2D)
    {
        doImageIO(mName, mGroup, ext, *loadedImages, this);

        // If this is a volumetric texture set the texture type flag accordingly.
        if ((*loadedImages)[0].getDepth() > 1)
        {
            U2_EXCEPT(U2Exception::ERR_NOT_IMPLEMENTED,
                        "**** Unsupported 3D texture type ****",
                        "GLESTexture::prepare" );
        }
        
		// If PVRTC and 0 custom mipmap disable auto mip generation and disable software mipmap creation
        PixelFormat imageFormat = (*loadedImages)[0].getFormat();
		if (imageFormat == PF_PVRTC_RGB2 || imageFormat == PF_PVRTC_RGBA2 ||
            imageFormat == PF_PVRTC_RGB4 || imageFormat == PF_PVRTC_RGBA4)
		{
            size_t imageMips = (*loadedImages)[0].getNumMipmaps();
            if (imageMips == 0)
            {
                mNumMipmaps = mNumRequestedMipmaps = imageMips;
                // Disable flag for auto mip generation
                mUsage &= ~TU_AUTOMIPMAP;
            }
		}
    }
    else
    {
        U2_EXCEPT(U2Exception::ERR_NOT_IMPLEMENTED,
                    "**** Unknown texture type ****",
                    "GLESTexture::prepare");
    }

    mLoadedImages = loadedImages;
}

void U2GLESTexture::unprepareImpl()
{
    mLoadedImages.setNull();
}

void U2GLESTexture::loadImpl()
{
    if (mUsage & TU_RENDERTARGET)
    {
        createRenderTexture();
        return;
    }

    // Now the only copy is on the stack and will be cleaned in case of
    // exceptions being thrown from _loadImages
    LoadedImages loadedImages = mLoadedImages;
    mLoadedImages.setNull();

    // Call internal _loadImages, not loadImage since that's external and 
    // will determine load status etc again
    ConstImagePtrList imagePtrs;

    for (size_t i = 0; i < loadedImages->size(); ++i)
    {
        imagePtrs.push_back(&(*loadedImages)[i]);
    }

    _loadImages(imagePtrs);
}

void U2GLESTexture::freeInternalResourcesImpl()
{
    mSurfaceList.clear();
    glDeleteTextures(1, &mTextureID);
    GL_CHECK_ERROR;
}

void U2GLESTexture::_createSurfaceList()
{
    mSurfaceList.clear();

    // For all faces and mipmaps, store surfaces as U2HardwarePixelBufferSharedPtr
    bool wantGeneratedMips = (mUsage & TU_AUTOMIPMAP) != 0;

    // Do mipmapping in software? (uses GLU) For some cards, this is still needed. Of course,
    // only when mipmap generation is desired.
    bool doSoftware = wantGeneratedMips && !mMipmapsHardwareGenerated && getNumMipmaps();

    for (size_t face = 0; face < getNumFaces(); face++)
    {
		size_t width = mWidth;
		size_t height = mHeight;
        for (size_t mip = 0; mip <= getNumMipmaps(); mip++)
        {
            GLESHardwarePixelBuffer *buf = U2_NEW GLESTextureBuffer(mName,
                                                                 getGLESTextureTarget(),
                                                                 mTextureID,
                                                                 width, height,
                                                                 U2GLESPixelUtil::getClosestGLInternalFormat(mFormat, mHwGamma),
                                                                 U2GLESPixelUtil::getGLOriginDataType(mFormat),
                                                                 face,
                                                                 mip,
                                                                 static_cast<U2HardwareBuffer::Usage>(mUsage),
                                                                 doSoftware && mip==0, mHwGamma, mFSAA);

            mSurfaceList.push_back(U2HardwarePixelBufferSharedPtr(buf));

            /// Check for error
            if (buf->getWidth() == 0 ||
                buf->getHeight() == 0 ||
                buf->getDepth() == 0)
            {
                U2_EXCEPT(
                    U2Exception::ERR_RENDERINGAPI_ERROR, 
                    "Zero sized texture surface on texture " + getName()
                    + " face "  +U2StringConverter::toString(face)
                    + " mipmap " + U2StringConverter::toString(mip)
                    + ". The GL driver probably refused to create the texture.", 
                    "GLESTexture::_createSurfaceList");
            }
        }
    }
}

U2HardwarePixelBufferSharedPtr U2GLESTexture::getBuffer(size_t face, size_t mipmap)
{
    if (face >= getNumFaces())
    {
        U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS,
                    "Face index out of range",
                    "GLESTexture::getBuffer");
    }

    if (mipmap > mNumMipmaps)
    {
        U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS,
                    "Mipmap index out of range",
                    "GLESTexture::getBuffer");
    }

    unsigned int idx = face * (mNumMipmaps + 1) + mipmap;
    assert(idx < mSurfaceList.size());
    return mSurfaceList[idx];
}
